package com.example.console;

import com.example.console.anno.ConsoleBean;
import com.example.console.anno.ConsoleCommand;
import com.example.console.exception.CommandException;
import com.example.console.impl.MethodCommand;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

/**
 * 控制台对象
 *
 * @author frank
 */
public class Console {

    private static final Logger logger = LoggerFactory.getLogger(Console.class);

    private final ObjectMapper mapper = new ObjectMapper();
    /** 控制台停止状态 */
    private boolean stop;
    /** 控制台指令集合 */
    private Map<String, Command> commands = new TreeMap<>();

    /** 控制台管理的当前的应用上下文 */
    protected ConfigurableApplicationContext applicationContext = null;
    /** 钩子方法*/
    private Thread shutdownHook = null;
    /** Spring App*/
    protected SpringApplication springApplication;

    /**
     *
     * @param clazz Spring Boot 启动类(Main方法所在的类)
     * @param args Main方法参数
     */
    public Console(Class<?> clazz, String[] args) {
        try {
            this.springApplication = new SpringApplication(clazz);
            this.springApplication.setRegisterShutdownHook(false);
            this.applicationContext = springApplication.run(args);
            // 注册 JVM ShutdownHook
            registerShutdownHook(this::stop);
            registerCommand();
            commands.put(cmdStop.name(), cmdStop);
            commands.put(cmdList.name(), cmdList);
        } catch (Exception e) {
            logger.error("Spring容器初始化失败！");
            if (applicationContext != null && applicationContext.isRunning()) {
                applicationContext.close();
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("控制台初始化完成");
        }
    }

    public Console() {
        try {
            // 注册 JVM ShutdownHook
            registerShutdownHook(this::stop);
            registerCommand();
            commands.put(cmdStop.name(), cmdStop);
            commands.put(cmdList.name(), cmdList);
        } catch (Exception e) {
            logger.error("Spring容器初始化失败！");
            if (applicationContext != null && applicationContext.isRunning()) {
                applicationContext.close();
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("控制台初始化完成");
        }
    }

    protected void registerShutdownHook(ShutdownHook hook) {
        Runtime runtime = Runtime.getRuntime();
        if (this.shutdownHook != null) {
            removeShutdownHook();
        }
        this.shutdownHook = new Thread(() -> hook.shutdown());
        runtime.addShutdownHook(this.shutdownHook);
    }

    protected void removeShutdownHook() {
        try {
            Runtime runtime = Runtime.getRuntime();
            if (this.shutdownHook != null) {
                runtime.removeShutdownHook(shutdownHook);
            }
        } catch (Exception e) {
        }
    }

    /**
     * 停止控制台的方法
     */
    public void stop() {
        stop = true;
        removeShutdownHook();
        applicationContext.close();
        if (logger.isInfoEnabled()) {
            logger.info("控制台关闭");
        }
    }

    /**
     * 检查控制台是否已经停止
     *
     * @return
     */
    public boolean isStop() {
        return stop;
    }

    /**
     * 获取指令名对应的命令对象实例
     *
     * @param name
     * @return
     */
    public Command getCommand(String name) {
        return commands.get(name);
    }

    /**
     * 启动控制台
     */
    public ConfigurableApplicationContext start() {
        ConsoleRunner runner = new ConsoleRunner(this);
        Thread thread = new Thread(runner, "控制台输入线程");
        thread.start();
        if (logger.isInfoEnabled()) {
            logger.info("控制台启动");
        }
        return applicationContext;
    }

    /**
     * 命令方法过滤器
     */
    private MethodFilter findCommand = new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            if (method.getAnnotation(ConsoleCommand.class) != null) {
                return true;
            }
            return false;
        }
    };

    /**
     * 注册控制台命令
     */
    private void registerCommand() {
        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(ConsoleBean.class);
        for (Entry<String, Object> entry : beans.entrySet()) {
            final Object bean = entry.getValue();
            ReflectionUtils.doWithMethods(bean.getClass(), new MethodCallback() {
                @Override
                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                    Command command = new MethodCommand(bean, method, mapper);
                    commands.put(command.name(), command);
                }
            }, findCommand);
        }
    }

    /** 停止控制台的指令 */
    private Command cmdStop = new Command() {
        @Override
        public String name() {
            return "stop";
        }

        @Override
        public void execute(String[] arguments) throws CommandException {
            stop();
        }

        @Override
        public String description() {
            return "停止控制台";
        }
    };
    /** 列出全部控制台指令的指令 */
    private Command cmdList = new Command() {
        @Override
        public String name() {
            return "ls";
        }

        @Override
        public void execute(String[] arguments) throws CommandException {
            for (Entry<String, Command> entry : commands.entrySet()) {
                Command command = entry.getValue();
                System.out.println(command.name() + "\t:\t" + command.description());
            }
        }

        @Override
        public String description() {
            return "列出全部控制台指令";
        }
    };

    public ConfigurableApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public SpringApplication getSpringApplication() {
        return springApplication;
    }
}
